# -*-coding:utf-8-*-

import json
import time
import re
import random
from pyrestful import mediatypes
from pyrestful.rest import get, post, put, delete
from bson.objectid import ObjectId
from handlers.base import PayBaseHandler
from utils.auth import authenticated
from utils.sign import signed
import settings
from alidayu.topnado import Topnado
import hashlib
from utils.misc import dict_from_xml, dict_to_xml
from bson.objectid import ObjectId
from utils.http import http_post
from tornado.httpclient import AsyncHTTPClient, HTTPRequest, HTTPClient
from tornado.gen import coroutine, Return
from tornado.web import asynchronous
import logging

import base64
import binascii
from Crypto import Random
from Crypto.Hash import SHA256
from Crypto.Cipher import DES
from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
from Crypto.Signature import PKCS1_v1_5 as Signature_pkcs1_v1_5
from Crypto.PublicKey import RSA
import urlparse
import urllib


ALIPAY_GATEWAY = 'https://openapi.alipay.com/gateway.do'
ALIPAY_GATEWAY_SANDBOX = 'https://openapi.alipaydev.com/gateway.do'


class Alipay(object):
    def __init__(self, appid, app_pri_key, srv_pub_key, method, notify_url=None, return_url=None):
        self._app_pri_key = app_pri_key
        self._srv_pub_key = srv_pub_key
        self._common_args = {
            'app_id': appid,
            'method': method,
            # 'format': 'JSON',
            'charset': 'utf-8',
            'sign_type': 'RSA2',
            'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),
            'version': '1.0',
        }
        if notify_url is not None:
            self._common_args.update(notify_url=notify_url)
        if return_url is not None:
            self._common_args.update(return_url=return_url)

    def set_biz_content(self, **kwargs):
        biz_content = json.dumps(kwargs, separators=(',', ':'))
        self._common_args.update(biz_content=biz_content)

    def get_signed_string(self):
        ordered_data = sorted([(k, v)
                               for k, v in self._common_args.iteritems()])
        unsigned_string = '&'.join(
            ['{0}={1}'.format(k, v) for k, v in ordered_data])
        # print unsigned_string
        sign = Alipay._rsa_sign(unsigned_string, self._app_pri_key)
        # print sign
        # print '&'.join(
        #    ['{0}={1}'.format(k, v) for k, v in ordered_data]) + '&sign=' + sign
        quoted_string = '&'.join(
            ['{0}={1}'.format(k, urllib.quote_plus(v)) for k, v in ordered_data])
        signed_string = quoted_string + '&sign=' + urllib.quote_plus(sign)
        return signed_string

    @staticmethod
    def verify_sign_from_query(query, key, pop_sign_type=True):
        d = urlparse.parse_qs(query)
        d = dict([(k, v[-1]) for k, v in d.iteritems()])
        sign = d.pop('sign')
        # 生活号异步通知组成的待验签串里需要保留sign_type参数。
        if pop_sign_type:
            d.pop('sign_type', None)
        Alipay._verify_sign_from_dict(d, key, sign)
        return d

    @staticmethod
    def verify_sign_from_body(body, key, method):
        sign = body['sign']
        d = body[method.replace('.', '_')]
        Alipay._verify_sign_from_dict(d, key, sign)
        return d

    @staticmethod
    def _verify_sign_from_dict(d, key, sign):
        ordered_data = sorted([(k, v) for k, v in d.iteritems()])
        unquoted_string = '&'.join(
            ['{0}={1}'.format(k, urllib.unquote_plus(v)) for k, v in ordered_data])
        Alipay._rsa_verify(unquoted_string, key, sign)

    @staticmethod
    def _normalize_key(key, is_private=True):
        BEGIN_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----'
        END_PRIVATE_KEY = '-----END PRIVATE KEY-----'
        BEGIN_PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----'
        END_PUBLIC_KEY = '-----END PUBLIC KEY-----'
        if is_private:
            key = '%s\n%s\n%s' % (BEGIN_PRIVATE_KEY, key, END_PRIVATE_KEY)
        else:
            key = '%s\n%s\n%s' % (BEGIN_PUBLIC_KEY, key, END_PUBLIC_KEY)
        return key

    @staticmethod
    def _rsa_sign(message, key):
        key = Alipay._normalize_key(key, True)
        rsakey = RSA.importKey(key)
        signer = Signature_pkcs1_v1_5.new(rsakey)
        digest = SHA256.new(message)
        sign = signer.sign(digest)
        return base64.b64encode(sign)

    @staticmethod
    def _rsa_verify(message, key, signature):
        key = Alipay._normalize_key(key, False)
        rsakey = RSA.importKey(key)
        verifier = Signature_pkcs1_v1_5.new(rsakey)
        digest = SHA256.new(message)
        if not verifier.verify(digest, base64.b64decode(signature)):
            raise Exception('signature verify fail')


class AlipayHandler(PayBaseHandler):
    def __init__(self, application, request, **kwargs):
        super(AliPayHandler, self).__init__(application, request, **kwargs)

    @coroutine
    def update_pay_status(self, order, response):
        if oid != response['out_trade_no'] or str(order.total_price) != response['total_amount']:
            raise Exception('invalid response')
        trade_status = response['trade_status']
        if trade_status == 'TRADE_SUCCESS' and trade_status == 'TRADE_FINISHED':
            pay_info = {
                'alipay': response,
            }
            result = yield self.set_order_paid(order, paid_by='alipay', pay_info=pay_info)
            raise Return(result)
        else:
            self.logger.error('trade_status: %s' % trade_status)
            raise Return(False)

        '''
        示例
        https://api.xx.com/receive_notify.htm?total_amount=2.00&buyer_id=2088102116773037&body=大乐透2.1&trade_no=2016071921001003030200089909&refund_fee=0.00&notify_time=2016-07-19 14:10:49&subject=大乐透2.1&sign_type=RSA2&charset=utf-8&notify_type=trade_status_sync&out_trade_no=0719141034-6418&gmt_close=2016-07-19 14:10:46&gmt_payment=2016-07-19 14:10:47&trade_status=TRADE_SUCCESS&version=1.0&sign=kPbQIjX+xQc8F0/A6/AocELIjhhZnGbcBN6G4MM/HmfWL4ZiHM6fWl5NQhzXJusaklZ1LFuMo+lHQUELAYeugH8LYFvxnNajOvZhuxNFbN2LhF0l/KL8ANtj8oyPM4NN7Qft2kWJTDJUpQOzCzNnV9hDxh5AaT9FPqRS6ZKxnzM=&gmt_create=2016-07-19 14:10:44&app_id=2015102700040153&seller_id=2088102119685838&notify_id=4a91b7a78a503640467525113fb7d8bg8e
        验签流程
        1. 在通知返回参数列表中，除去sign、sign_type两个参数外，凡是通知返回回来的参数皆是待验签的参数。
        2. 将剩下参数进行url_decode, 然后进行字典排序，组成字符串，得到待签名字符串。
        3. 将签名参数（sign）使用base64解码为字节码串。
        4. 使用RSA的验签方法，通过签名字符串、签名参数（经过base64解码）及支付宝公钥验证签名，根据返回结果判定是否验签通过。
        '''
    @post(_path='/platform/api/orders/{oid}/alipay/pay/on_notify', _types=[str])
    @coroutine
    def alipay_pay_on_notify(self, oid):
        order = yield self.orders_dao.get_record_by_id(oid)
        if order is None:
            raise Exception('invalid order id')
        if 'to_be_paid' == order.status:
            alipay_app_id = self.get_argument('app_id')
            self.app = yield self.apps_dao.get_app_by_alipay_app_id(alipay_app_id)
            (_, _, _, srv_pub_key) = self._get_app_args()
            notification = Alipay.verify_sign_from_query(
                self.request.query, srv_pub_key)
            self.logger.info(json.dumps(notification))
            self.user_id = notification['passback_params']
            yield self.update_pay_status(order, notification)
        self.finish('success')

    @get(_path='/platform/api/orders/{oid}/alipay/pay/prepay', types=[str])
    @authenticated
    @coroutine
    def get_alipay_pay_prepay(self, oid):
        trade_type = self.get_argument('trade_type', 'JSAPI')
        trade_type = str(trade_type)
        if trade_type not in ['JSAPI', 'MWEB']:
            raise Exception('unsupport trade type: %s' % trade_type)
        order = yield self.orders_dao.get_record_by_id(oid)
        if order is None:
            raise Exception('invalid order id')

        if 'to_be_paid' != order.status:
            raise Exception('order is paid')

        # NOTE: body, spbill_create_ip 等这个字段微信后台好像并没有进一步判断，使用
        return_url = self.get_argument('redirect_uri', None)
        notify_url = '%s://%s/platform/api/orders/%s/alipay/pay/on_notify' % (
            self.request.protocol, self.request.host, oid)

        self.logger.info('self.request.remote_ip: %s' % self.request.remote_ip)
        # self.logger.debug('user_id: %s, openid: %s', self.user_id, self.user.alipaymp_openid)

        args = {
            # 'body': '对一笔交易的具体描述信息。如果是多种商品，请将商品描述字符串累加传给body',
            # '商品的标题/交易标题/订单标题/订单关键字等',
            'subject': '支付宝支付',
            'out_trade_no': oid,
            # 'timeout_express': '15d', 相对超时
            # 'time_expire': 'yyyy-MM-dd HH:mm', 绝对超时
            'total_amount': str(order.total_price),  # [0.01,100000000]
            # 'auth_token': '',
            'goods_type': '1',  # 商品主类型：0—虚拟类商品，1—实物类商品注：虚拟类商品不支持使用花呗渠道
            'passback_params': self.user.id_str,
        }
        if 'JSAPI' == trade_type:
            args.update(product_code='QUICK_MSECURITY_PAY')
            method = 'alipay.trade.app.pay'
        elif 'MWEB' == trade_type:
            args.update(product_code='QUICK_WAP_WAY')
            method = 'alipay.trade.wap.pay'

        alipay_gateway, appid, app_pri_key, srv_pub_key = self._get_app_args()
        alipay = Alipay(appid, app_pri_key, srv_pub_key, method,
                        notify_url=notify_url, return_url=return_url)
        alipay.set_biz_content(**args)
        qs = alipay.get_signed_string()
        if 'JSAPI' == trade_type:
            self.respond_success(qs)
        elif 'MWEB' == trade_type:
            self.redirect(alipay_gateway + '?' + qs)

    def _get_app_args(self):
        if self.app.use_alipay_sandbox:
            alipay_gateway = ALIPAY_GATEWAY
            appid = self.app.alipay_app_id
            app_pri_key = self.app.alipay_rsa2_app_pri_key
            srv_pub_key = self.app.alipay_rsa2_srv_pub_key
        else:
            alipay_gateway = ALIPAY_GATEWAY_SANDBOX
            appid = self.app.alipay_sandbox_app_id
            app_pri_key = self.app.alipay_sandbox_rsa2_app_pri_key
            srv_pub_key = self.app.alipay_sandbox_rsa2_srv_pub_key
        return alipay_gateway, appid, app_pri_key, srv_pub_key

    @get(_path='/platform/api/orders/{oid}/alipay/pay/status', _types=[str])
    @authenticated
    @coroutine
    def get_alipay_pay_status(self, oid):
        order = yield self.orders_dao.get_record_by_id(oid)
        if order is None:
            raise Exception('invalid order id')
        status = 'paid'
        if 'to_be_paid' == order.status:
            args = {
                'out_trade_no': oid,
            }
            alipay_gateway, appid, app_pri_key, srv_pub_key = self._get_app_args()
            method = 'alipay.trade.query'
            alipay = Alipay(appid, app_pri_key, srv_pub_key, method)
            alipay.set_biz_content(**args)
            qs = alipay.get_signed_string()
            response = yield http_post(alipay_gateway, query=qs)
            self.logger.info(json.dumps(response))
            response = Alipay.verify_sign_from_body(
                response, srv_pub_key, method)
            result = yield self.update_pay_status(order, response)
            if not result:
                status = 'to_be_paid'
        self.respond_success({'status': status})


app_info = {
    'appid': '2016091700535282',
    'rsa2': {
        'app_pri': 'MIIEogIBAAKCAQEAuTPdvG4PxqfqFyEBT8MeQqWXZYBhBj2Zbs0NGRgI9uk4NFzaAoAdziDbwKUxIZaDDl2KRqO8pE6i+R8UyUxb/w6A6D1ARthk8UT2aSQazVm4wHNqtJvtsjVjY3CpSuN8BKlf6L8Vyd1Wu/siVHGnMtwMOZwyRaSDfOMrtT58mIbd4gouxn7CYjepdLFQu1lGoKPDWwRq7+TD9AT35fMpBFGO8t2xnkdSdx771O93uqm9Q18p22kpDKC7zPfuDIUiCswvVSc1Uc8oARAWietHHXF5+VtATuX9NB42wSKrhDwSWem6MUhf68Nr/65nhUVoyVkD7SKos0HiZl23H35BawIDAQABAoIBAF/ZhgLFe4i20qJRxJoBQ6H+jZJMlOpj6wq7OPIpzEntfdcrzvDIWaSawOC9HeZiPUWsB6pzh2gKArKn8VwVFu+0E+cd/CwuRl/P7y+PLgflTYbctPbwVF6M3vlSD2vgvAi1mBue+anV0dPML4JAp+XcmL8gJayBbkOuVqqgwl81XAFxSAvdTe2EgdDenYtFziI9g1iwDzROCKqgqzFG97YxJHGm3WegdbYS8rKtRPYzZKcoSmrt/U5O7oaptSzABeH/4wXPTXDj+RqY11PONvpL4b5Xy0MVzX5rZwTVeljen9tlMaN5cAmF073FODhMZgBr4Gd320gfU6ZfKVD0alECgYEA4txXftQetiOTHR6FWsjoLt4PBUtj5XiyeZfmvYE1z12fHZMCnltMLt5/zTfl2vP619SjwjaR7HwFsrVb6rbmL4QSibpVEsZeDIg+HauizwtJbJOMmREOo44MyY/lqEAvAzj2+4G4EiRFyO09mX8lsk0SbWgQloy5TcWiOZyW2v8CgYEA0P23zvV1orUurLC9ZnACQMYCo4mGhzbCurlGmBcLFUqMn+B46wd2J7KzjgiAMCuAZX8O1uL0Xlxf02Luz8QEsxSykxmtvvmgVx++YQz54Jn977QjzvcwAAbSXOcd8/TmPmgk/TSHiYTYqrK6yVz2Sgzb1+b2EEdkKFJlqUGmNZUCgYBa34B84WNb+mwXof4xG4FGJNVHfaeKaAq2Ak7/Yh637fyq0BSgclnV/HKdNeLxDgJRKnBef5V8cUGnEFa9cn5KUlFKskUZvDQ4OOsI35Pb8915jLEpL1f51BDzPJz2BJ/QXqUwogml2rnJfp7KOdBT1+Olx6W9aKlhqeUNxtf26QKBgDMGj1cvZP5J4FB3gna9SD+KWOCW1kqzGw496DeAAMu8/OKYCs3Pb5C3t9Eo0Ctyo5GkWPosRV+4jAjvccE2abNTjVoO/fXPus8qx++oHGYzEuXwhIGmpoiLIdde8lg65fA8uzM7HRB9Z+0dYC+Dy/o+c3qeQFuvIowZugp+JdeVAoGARcjIuQspvTPOUQTk2OyZa2ZwYVRSOyAGIXd9nh2TmJJ/NgttuwqpDpvCYbpSBjD+rv8JZbLN6gRO/ibmreniubvwt2GG8FvtM/YHBz5BwZPIsthdTHJ7zuKoyZbItwaW1yuaCN813vkXY8gN442Gpzdp0Yv2eitDnmOP0QgSpgQ=',
        'app_pub': 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuTPdvG4PxqfqFyEBT8MeQqWXZYBhBj2Zbs0NGRgI9uk4NFzaAoAdziDbwKUxIZaDDl2KRqO8pE6i+R8UyUxb/w6A6D1ARthk8UT2aSQazVm4wHNqtJvtsjVjY3CpSuN8BKlf6L8Vyd1Wu/siVHGnMtwMOZwyRaSDfOMrtT58mIbd4gouxn7CYjepdLFQu1lGoKPDWwRq7+TD9AT35fMpBFGO8t2xnkdSdx771O93uqm9Q18p22kpDKC7zPfuDIUiCswvVSc1Uc8oARAWietHHXF5+VtATuX9NB42wSKrhDwSWem6MUhf68Nr/65nhUVoyVkD7SKos0HiZl23H35BawIDAQAB',
        'srv_pub': 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3gWO8tFBQeUMzQED9LUOQcRzRMhaivhqD2ksB+SczIz1gFNFkJ/rStv3O17mx5sWj1c9nnyBFcKWiRET52528nQFZkHXDJnaEurncH6rE2oO2DoDw/LaF/ouEnW5D8MI7mo0k6pTiA4pJP3qkpACN73UDcAr6b+io2snbzqxGrwDaW9haNMfwRxO61BnDyXc2dI2y4ku0HWFpxAYjhM3/noqxZOs5FgbJWYcc0QhyxVhCsBQYLha2yCJV9yfvTod4lKimTE4OR3613JlYm0l5YB9bdFkhalenIzkvjJYQFR+hKH4lk7pq3MaQFqnozFyx/f78nEZ4U0H5ysreNSrLQIDAQAB',
    },
}


def test():
    print 'app_pri_key:\n%s' % app_info['rsa2']['app_pri']
    print 'app_pub_key:\n%s' % app_info['rsa2']['app_pub']
    method = 'alipay.trade.query'
    alipay = Alipay(app_info['appid'], app_info['rsa2']
                    ['app_pri'], app_info['rsa2']['srv_pub'], method)
    args = dict(out_trade_no='sdf')
    alipay.set_biz_content(**args)
    qs = alipay.get_signed_string()
    # 注意: 需要注释掉 Alipay_common_args.sign_type 字段以保证测试通过
    alipay.verify_sign_from_query(qs, app_info['rsa2']['app_pub'])


if '__main__' == __name__:
    test()
